package com.effectiv.req1.business.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;

import java.util.List;

import jodd.util.StringUtil;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.effectiv.req1.business.api.AbstractBusinessService;
import com.effectiv.req1.business.api.UseCaseService;
import com.effectiv.req1.domain.Actor;
import com.effectiv.req1.domain.Flow;
import com.effectiv.req1.domain.Frequency;
import com.effectiv.req1.domain.Lov;
import com.effectiv.req1.domain.Note;
import com.effectiv.req1.domain.Project;
import com.effectiv.req1.domain.Step;
import com.effectiv.req1.domain.UseCase;
import com.effectiv.req1.domain.User;
import com.effectiv.req1.event.AddUseCaseMainflowDescriptionEvent;
import com.effectiv.req1.event.NewUseCaseEvent;
import com.effectiv.req1.event.UpdateUseCaseEvent;
import com.effectiv.req1.repository.LovRepository;
import com.effectiv.req1.repository.ProjectRepository;
import com.effectiv.req1.repository.UseCaseRepository;
import com.effectiv.req1.repository.UserRepository;

@Service
@Slf4j
public class UseCaseServiceImpl extends
		AbstractBusinessService<UseCase, String> implements UseCaseService {

	@Autowired
	private UseCaseRepository repository;

	@Autowired
	private LovRepository lovRepository;

	@Autowired
	private ProjectRepository projectRepository;
	
	@Autowired
	private UserRepository userRepository;

	public List<UseCase> findUseCases() {
		List<UseCase> useCases = null;

		if (isCompanyAdmin()) {
			useCases = repository.findByCompany(getCompany());
		} else {
			List<Project> projects = projectRepository.findByCompanyAndUsers(
					getCompany(), getUser());
			log.info("Projects for the given user == {}", projects);
			useCases = repository.findByCompanyAndProjectIsIn(getCompany(),
					projects);
		}

		log.info("Use cases = {}", useCases);
		return useCases;
	}

	@Override
	public void save(UseCase t) {
		t.setCompany(getCompany());
		t.setMainFlow(new Flow());
		Lov status = lovRepository.findByName("CREATED");
		t.setStatus(status);

		super.save(t);
		
		NewUseCaseEvent event = BeanUtils.instantiate(NewUseCaseEvent.class);
		event.setUseCase(t);
		
		post(event);
	}

	@Override
	public UseCase update(UseCase useCase) {
		UseCase useCaseTarget = findOne(useCase.getId());
		UpdateUseCaseEvent descriptionChangeEvent = null;
		UpdateUseCaseEvent nameChangeEvent = null;
		UpdateUseCaseEvent statusChangeEvent = null;
		
		log.info("Old - {}", useCaseTarget.getDescription());
		log.info("New - {}", useCase.getDescription());
		
		if(!StringUtil.equals(useCaseTarget.getDescription(), useCase.getDescription())){
			
			log.info("Use Case description changed");
			
			useCaseTarget.setDescription(useCase.getDescription());
			descriptionChangeEvent = BeanUtils.instantiate(UpdateUseCaseEvent.class);
			descriptionChangeEvent.setField("description");
			descriptionChangeEvent.setOldValue(useCaseTarget.getDescription());
			descriptionChangeEvent.setNewValue(useCase.getDescription());
			descriptionChangeEvent.setUseCase(useCaseTarget);
		}
		
		log.info("Old - {}", useCaseTarget.getName());
		log.info("New - {}", useCase.getName());
		
		if(!StringUtil.equals(useCaseTarget.getName(), useCase.getName())){
			
			log.info("Use Case Name changed");
			
			
			useCaseTarget.setName(useCase.getName());
			nameChangeEvent = BeanUtils.instantiate(UpdateUseCaseEvent.class);
			nameChangeEvent.setField("name");
			nameChangeEvent.setOldValue(useCaseTarget.getName());
			nameChangeEvent.setNewValue(useCase.getName());
			nameChangeEvent.setUseCase(useCaseTarget);
		}
		
		

		Lov status = useCaseTarget.getStatus();
		if (StringUtil.equals(status.getName(), "CREATED")) {
			status = lovRepository.findByName("AUTHORING_IN_PROGRESS");
			useCaseTarget.setStatus(status);
			
			statusChangeEvent = BeanUtils.instantiate(UpdateUseCaseEvent.class);
			statusChangeEvent.setField("status");
			statusChangeEvent.setOldValue("CREATED");
			statusChangeEvent.setNewValue("AUTHORING_IN_PROGRESS");
			statusChangeEvent.setUseCase(useCaseTarget);
		}

		post(descriptionChangeEvent);
		post(nameChangeEvent);
		post(statusChangeEvent);
		
		return super.update(useCaseTarget);
	}

	@Override
	public UseCase addUseCaseMainFlowStepDescription(String id,
			String description) {
		UseCase useCase = findOne(id);
		List<Step> steps = useCase.getMainFlow().getSteps();

		if (steps == null) {
			steps = new ArrayList<Step>();
		}

		Step step = BeanUtils.instantiate(Step.class);
		step.setDescription(description);
		steps.add(step);
		useCase.getMainFlow().setSteps(steps);

		super.update(useCase);
		
		AddUseCaseMainflowDescriptionEvent event = BeanUtils.instantiate(AddUseCaseMainflowDescriptionEvent.class);
		event.setUseCase(useCase);
		event.setStep(description);
		event.setStepNo(steps.size() + 1);
		post(event);
		
		return useCase;
	}

	@Override
	public UseCase move(String id, int index, boolean up) {
		UseCase useCase = findOne(id);
		List<Step> steps = useCase.getMainFlow().getSteps();

		if (up) {
			Collections.swap(steps, index - 1, index);
		} else {
			Collections.swap(steps, index, index + 1);
		}
		useCase.getMainFlow().setSteps(steps);

		super.update(useCase);

		return useCase;
	}

	@Override
	public UseCase deleteMainFlowStep(String id, int index) {
		UseCase useCase = findOne(id);
		List<Step> steps = useCase.getMainFlow().getSteps();
		steps.remove(index);
		useCase.getMainFlow().setSteps(steps);
		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase addAlternateFlow(String id, int index) {
		UseCase useCase = findOne(id);
		Flow flow = BeanUtils.instantiate(Flow.class);
		flow.setAlternate(true);
		flow.setCreatedBy(getUser());
		flow.setLastUpdatedBy(getUser());

		List<Step> steps = useCase.getMainFlow().getSteps();
		Step step = steps.get(index);

		List<Flow> alternates = step.getAlternates();

		if (alternates == null) {
			alternates = new ArrayList<Flow>();
		}

		alternates.add(flow);
		step.setAlternates(alternates);
		useCase.getMainFlow().setSteps(steps);
		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase addAlternateFlowStepDescription(String description,
			String id, int stepid, int flowid) {
		UseCase useCase = findOne(id);
		Flow alternateFlow = useCase.getMainFlow().getSteps().get(stepid)
				.getAlternates().get(flowid);

		List<Step> altFlowSteps = alternateFlow.getSteps();

		if (altFlowSteps == null) {
			altFlowSteps = new ArrayList<Step>();
		}

		Step step = BeanUtils.instantiate(Step.class);
		step.setDescription(description);
		altFlowSteps.add(step);

		alternateFlow.setSteps(altFlowSteps);
		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase moveAlternate(String id, int index, int flowid, int stepid,
			boolean up) {
		UseCase useCase = findOne(id);

		Flow alternateFlow = useCase.getMainFlow().getSteps().get(stepid)
				.getAlternates().get(flowid);

		List<Step> altFlowSteps = alternateFlow.getSteps();
		if (up) {
			Collections.swap(altFlowSteps, index - 1, index);
		} else {
			Collections.swap(altFlowSteps, index, index + 1);
		}
		alternateFlow.setSteps(altFlowSteps);

		super.update(useCase);
		return useCase;
	}

	@Override
	protected void initInternal() {
		setRepository(repository);
	}

	@Override
	public UseCase deleteAlternateStep(String id, int index, int flowid,
			int stepid) {
		UseCase useCase = findOne(id);

		Flow alternateFlow = useCase.getMainFlow().getSteps().get(stepid)
				.getAlternates().get(flowid);

		List<Step> altFlowSteps = alternateFlow.getSteps();

		altFlowSteps.remove(index);

		alternateFlow.setSteps(altFlowSteps);

		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase appendCondition(String id, String description, boolean post) {
		UseCase useCase = findOne(id);

		if (post) {
			List<String> postConditions = useCase.getPostConditions();
			if (postConditions == null) {
				postConditions = new ArrayList<String>();
			}

			postConditions.add(description);
			useCase.setPostConditions(postConditions);
		}
		else{
			List<String> preConditions = useCase.getPreConditions();

			if (preConditions == null) {
				preConditions = new ArrayList<String>();
			}

			preConditions.add(description);
			useCase.setPreConditions(preConditions);
		}

		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase appendActor(String id, int index) {
		UseCase useCase = findOne(id);
		
		List<Actor> actorsInProject  = useCase.getProject().getActors();
		
		
		List<Actor> actors = useCase.getActors();
		if(actors == null){
			actors = new ArrayList<Actor>();
		}
		
		Actor actor = actorsInProject.get(index);
		
		log.info("selected actor = {}", actor);
		
		if(!actors.contains(actor)){
		
			actors.add(actor);
			useCase.setActors(actors);

			super.update(useCase);
		}
		else{
			//notify warning - its already present
		}
		return useCase;
	}

	@Override
	public UseCase editMainFlowStep(String id, int rowNum, String description) {
		UseCase useCase = findOne(id);
		Step step = useCase.getMainFlow().getSteps().get(rowNum-1);
		
		step.setDescription(description);
		
		useCase.getMainFlow().getSteps().set(rowNum-1, step);
		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase deleteCondition(String id, int index, boolean pre) {
		UseCase useCase = findOne(id);
		List<String> conditions = null;
		if(pre){
			conditions = useCase.getPreConditions();
			conditions.remove(index);
			useCase.setPreConditions(conditions);
		}
		else{
			conditions = useCase.getPostConditions();
			conditions.remove(index);
			useCase.setPostConditions(conditions);
		}
		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase addPriority(String id, String priority) {
		UseCase useCase = findOne(id);
		useCase.setPriority(priority);
		super.update(useCase);
		return useCase;
	
	}

	@Override
	public UseCase saveFrequency(String id, int times, String unit) {
		UseCase useCase = findOne(id);
		Frequency frequency = useCase.getFrequency();
		
		if(frequency == null){
			frequency = BeanUtils.instantiate(Frequency.class);
			
		}
		frequency.setTimes(times);
		frequency.setUnit(unit);
		
		useCase.setFrequency(frequency);
		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase addAssumption(String id, String description) {
		UseCase useCase = findOne(id);
		List<String> assumptions = useCase.getAssumptions();
		
		if(assumptions == null){
			assumptions = new ArrayList<String>();
		}
		
		assumptions.add(description);
		useCase.setAssumptions(assumptions);
		
		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase deleteAssumption(String id, int index) {
		UseCase useCase = findOne(id);
		List<String> assumptions = useCase.getAssumptions();
		
		if(assumptions != null){
			assumptions.remove(index);
			useCase.setAssumptions(assumptions);
			super.update(useCase);
		}
		
		return useCase;
	}

	@Override
	public UseCase deleteActor(String id, int index) {
		UseCase useCase = findOne(id);
		List<Actor> actors = useCase.getActors();
		
		if(actors != null){
			actors.remove(index);
			useCase.setActors(actors);
			super.update(useCase);
		}
		
		return useCase;
	}

	@Override
	public UseCase addException(String id, String description) {
		UseCase useCase = findOne(id);
		List<String> exceptions = useCase.getExceptions();
		
		if(exceptions == null){
			exceptions = new ArrayList<String>();
		}
		exceptions.add(description);
		useCase.setExceptions(exceptions);
		
		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase deleteException(String id, int index) {
		UseCase useCase = findOne(id);
		List<String> exceptions = useCase.getExceptions();
		
		if(exceptions != null){
			exceptions.remove(index);
			useCase.setExceptions(exceptions);
			super.update(useCase);
		}
		
		return useCase;
	}

	@Override
	public List<UseCase> findByProject(String projectId) {
		Project project = projectRepository.findOne(projectId);
		
		List<UseCase> useCases = repository.findByCompanyAndProject(getCompany(), project);
		return useCases;
	}

	@Override
	public UseCase includeUseCase(String id, String includedId) {
		
		UseCase useCase = findOne(id);
		UseCase include = findOne(includedId);
		
		List<UseCase> includes = useCase.getIncludes();
		
		if(includes == null){
			includes = new ArrayList<UseCase>();
		}
		
		includes.add(include);
		useCase.setIncludes(includes);
		super.update(useCase);		
		return useCase;
	}
	
	@Override
	public UseCase deleteInclude(String id, int index) {
		UseCase useCase = findOne(id);
		List<UseCase> includes = useCase.getIncludes();
		
		if(includes != null){
			includes.remove(index);
			useCase.setIncludes(includes);
			super.update(useCase);
		}
		
		return useCase;
	}

	@Override
	public UseCase addSpecial(String id, String description) {
		UseCase useCase = findOne(id);
		List<String> specialRequirements = useCase.getSpecialRequirements();
		
		if(specialRequirements == null){
			specialRequirements = new ArrayList<String>();
		}
		specialRequirements.add(description);
		useCase.setSpecialRequirements(specialRequirements);
		
		super.update(useCase);
		return useCase;
	}

	@Override
	public UseCase deleteSpecial(String id, int index) {
		UseCase useCase = findOne(id);
		List<String> specialRequirements = useCase.getSpecialRequirements();
		
		if(specialRequirements != null){
			specialRequirements.remove(index);
			useCase.setSpecialRequirements(specialRequirements);
			super.update(useCase);
		}
		
		return useCase;
	}

	@Override
	public UseCase addNote(String id, String responsibleUserId,
			Date targetDate, String description,String priority) {
		
		UseCase useCase = findOne(id);
		User responsible = userRepository.findOne(responsibleUserId);
		
		Note note = BeanUtils.instantiate(Note.class);
		note.setDescription(description);
		note.setResponsible(responsible);
		note.setStatus("CREATED");
		note.setPriority(priority);
		note.setTargetDate(targetDate);
		note.setType("NOTE");
		note.setCreatedBy(getUser());
		note.setLastModifiedBy(getUser());
		
		List<Note> notes = useCase.getNotes();
		
		if(notes == null){
			notes = new ArrayList<Note>();
		}
		
		notes.add(note);
		useCase.setNotes(notes);
		super.update(useCase);
		
		return useCase;
	}
	
	
}
